Es ist möglich, das FreeCAD-Modul in eine Python-Anwendung zu importieren und alle seine Werkzeuge aus der Host-Anwendung heraus zu verwenden, aber auch die grafische Benutzeroberfläche (GUI) kann als Python-Modul importiert werden. Normalerweise kann nur die gesamte Schnittstelle als Ganzes importiert werden, nicht einzelne Teile davon. Der Grund dafür ist, dass das FreeCAD-Schnittstellensystem nicht nur aus unabhängigen Widgets und Symbolleisten besteht, sondern eine komplexe Konstruktion ist, bei der mehrere unsichtbare Komponenten (wie das Auswahlsystem usw.) erforderlich sind, damit die Haupt-3D-Ansicht funktionieren kann.
Mit ein wenig Hacking ist es jedoch möglich, die gesamte FreeCAD-Oberfläche zu importieren und dann die 3D-Ansicht daraus in Ihre eigene Qt-Anwendung zu verschieben. Wir zeigen hier drei verschiedene Methoden.
Man beachte, dass dieser Ansatz viele Probleme mit sich bringt. Die Qt-Ereignisbehandlung scheint nicht zu funktionieren (keine Ahnung warum) und wenn man das Kontextmenü der 3D-Ansicht verwendet, stürzt die Anwendung ab. Eine bessere Möglichkeit wäre, eine eigene 3D-Ansicht SoQtExaminerViewer oder SoQtViewer zu erstellen und den Inhalt der 3D-Ansicht von FreeCAD in diese Ansicht zu "pushen", wie in den anderen Abschnitten unten gezeigt.
Zuerst das Hauptfenster über PySide holen:
from PySide import QtGui
from PySide import QtCore
def getMainWindow():
toplevel = QtGui.qApp.topLevelWidgets()
for i in toplevel:
if i.metaObject().className() == "Gui::MainWindow":
return i
raise Exception("No main window found")
mw = getMainWindow()
Dann die Ansicht View3DInventor auf die gleiche Weise aufrufen:
def get3dview(mw):
childs=mw.findChildren(QtGui.QMainWindow)
for i in childs:
if i.metaObject().className() == "Gui::View3DInventor":
return i
return None
v = get3dview(mw)
Der folgende Code wird automatisch generiert, indem eine Ui-Datei mit QtDesigner erstellt, und mit dem pyuic-Tool in Python-Code konvertiert wird:
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'mainwindow.ui'
#
# Created: Sun Dec 27 11:18:56 2009
# by: PySide UI code generator 4.6
#
# Modify for PySide 11/02/2015
# Python version: 2.7.8
# Qt version: 4.8.6
#
# WARNING! All changes made in this file will be lost!
from PySide import QtCore, QtGui
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(508, 436)
self.centralwidget = QtGui.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.gridLayout = QtGui.QGridLayout(self.centralwidget)
self.gridLayout.setObjectName("gridLayout")
self.mdiArea = QtGui.QMdiArea(self.centralwidget)
self.mdiArea.setViewMode(QtGui.QMdiArea.TabbedView)
self.mdiArea.setTabPosition(QtGui.QTabWidget.South)
self.mdiArea.setObjectName("mdiArea")
self.gridLayout.addWidget(self.mdiArea, 0, 0, 1, 1)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtGui.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 508, 27))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtGui.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(QtGui.QApplication.translate("MainWindow", "MainWindow", None, QtGui.QApplication.UnicodeUTF8))
Dann ein Hauptfenster erstellen, das das Hauptfenster der Anwendung sein soll, die oben beschriebene UI-Einrichtung darauf anwenden, um einen MDI-Bereich hinzuzufügen und unsere 3D-Ansicht dorthin zu "verschieben".
ui = Ui_MainWindow()
my_mw = QtGui.QMainWindow()
ui.setupUi(my_mw)
ui.mdiArea.addSubWindow(v)
my_mw.show()
Alternativ kann auch das FreeCADGui-Modul verwendet werden, um eine OpenInventor (Coin)-Darstellung der Objekte der Szene zu extrahieren und diese Daten dann in einem externen Viewer (der Anwendung) zu verwenden. Hier ist eine einfache Möglichkeit, die 3D-Darstellung eines Objekts zu erhalten.
from pivy import coin
import FreeCAD as App
box = App.activeDocument().addObject("Part::Box", "myBox")
s = box.ViewObject.toString() # store as string
inp = coin.SoInput()
inp.setBuffer(s)
myNode = coin.SoDB.readAll(inp) # restore from string
Dann einen eigenständigen Viewer mit Pivy erstellen:
from pivy.sogui import *
from pivy.coin import *
import sys
def myViewer():
# Initialize Coin. This returns a main window to use.
# If unsuccessful, exit.
myWindow = SoGui.init(sys.argv[0])
if myWindow == None:
sys.exit(1)
# Make an empty scene and add our node to it
scene = SoSeparator()
scene.addChild(myNode)
# Create a viewer in which to see our scene graph.
viewer = SoGuiExaminerViewer(myWindow)
# Put our scene into viewer, change the title
viewer.setSceneGraph(scene)
viewer.setTitle("FreeCAD Object Viewer")
viewer.show()
SoGui.show(myWindow) # Display main window
SoGui.mainLoop() # Main Coin event loop
Dann muss nur noch der Viewer gestartet werden:
myViewer()
Anstelle des Sogui-Viewers kann man auch das modernere quarter-Modul verwenden. Dies ist im Allgemeinen die beste der drei Optionen.
#!/usr/bin/env python
###
# Copyright (c) 2002-2008 Kongsberg SIM
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
import os
import sys
from PySide import QtCore, QtGui
from PySide.QtGui import QMainWindow, QWorkspace, QAction, QFileDialog, QApplication
from pivy.coin import SoInput, SoDB
from pivy.quarter import QuarterWidget
import FreeCAD, FreeCADGui
def getMainWindow():
toplevel = QtGui.qApp.topLevelWidgets()
for i in toplevel:
if i.metaObject().className() == "Gui::MainWindow":
return i
raise Exception("No main window found")
class MdiQuarterWidget(QuarterWidget):
def __init__(self, parent, sharewidget):
QuarterWidget.__init__(self, parent=parent, sharewidget=sharewidget)
def loadFile(self, filename):
in_ = SoInput()
if (in_.openFile(str(filename.toLatin1()))):
root = SoDB.readAll(in_)
if (root):
self.setSceneGraph(root)
self.currentfile = filename
self.setWindowTitle(filename)
return True
return False
def currentFile(self):
return self.currentfile
def minimumSizeHint(self):
return QtCore.QSize(640, 480)
class MdiMainWindow(QMainWindow):
def __init__(self, qApp):
QMainWindow.__init__(self)
self._firstwidget = None
self._workspace = QWorkspace()
self.setCentralWidget(self._workspace)
self.setAcceptDrops(True)
self.setWindowTitle("Pivy Quarter MDI example")
filemenu = self.menuBar().addMenu("&File")
windowmenu = self.menuBar().addMenu("&Windows")
fileopenaction = QAction("&Create Box", self)
fileexitaction = QAction("E&xit", self)
tileaction = QAction("Tile", self)
cascadeaction = QAction("Cascade", self)
filemenu.addAction(fileopenaction)
filemenu.addAction(fileexitaction)
windowmenu.addAction(tileaction)
windowmenu.addAction(cascadeaction)
self.connect(fileopenaction, QtCore.SIGNAL("triggered()"), self.createBoxInFreeCAD)
self.connect(fileexitaction, QtCore.SIGNAL("triggered()"), QtGui.qApp.closeAllWindows)
self.connect(tileaction, QtCore.SIGNAL("triggered()"), self._workspace.tile)
self.connect(cascadeaction, QtCore.SIGNAL("triggered()"), self._workspace.cascade)
windowmapper = QtCore.QSignalMapper(self)
self.connect(windowmapper, QtCore.SIGNAL("mapped(QWidget *)"), self._workspace.setActiveWindow)
self.dirname = os.curdir
def dragEnterEvent(self, event):
# just accept anything...
event.acceptProposedAction()
def dropEvent(self, event):
mimedata = event.mimeData()
if mimedata.hasUrls():
path = mimedata.urls().takeFirst().path()
self.open_path(path)
def closeEvent(self, event):
self._workspace.closeAllWindows()
def open(self):
self.open_path(QFileDialog.getOpenFileName(self, "", self.dirname))
def open_path(self, filename):
self.dirname = os.path.dirname(str(filename.toLatin1()))
if not filename.isEmpty():
existing = self.findMdiChild(filename)
if existing:
self._workspace.setActiveWindow(existing)
return
child = self.createMdiChild()
if (child.loadFile(filename)):
self.statusBar().showMessage("File loaded", 2000)
child.show()
else:
child.close()
def findMdiChild(self, filename):
canonicalpath = QtCore.QFileInfo(filename).canonicalFilePath()
for window in self._workspace.windowList():
mdiwidget = window
if mdiwidget.currentFile() == canonicalpath:
return mdiwidget
return 0;
def createMdiChild(self):
widget = MdiQuarterWidget(None, self._firstwidget)
self._workspace.addWindow(widget)
if not self._firstwidget:
self._firstwidget = widget
return widget
def createBoxInFreeCAD(self):
widget = MdiQuarterWidget(None, self._firstwidget)
self._workspace.addWindow(widget)
if not self._firstwidget:
self._firstwidget = widget
widget.show()
doc = FreeCAD.newDocument()
doc.addObject("Part::Box","myBox")
iv_=FreeCADGui.getDocument(doc.Name).getObject("myBox").toString()
in_ = SoInput()
in_.setBuffer(iv_)
root = SoDB.readAll(in_)
if (root):
widget.setSceneGraph(root)
def main():
app = QApplication(sys.argv)
mdi = MdiMainWindow(app)
mdi.show()
FreeCADGui.showMainWindow() # setup the GUI stuff of FreeCAD
mw=getMainWindow()
mw.hide() # hide all
if len(sys.argv)==2:
mdi.open_path(QtCore.QString(sys.argv[1]))
sys.exit(app.exec_())
def show():
mdi = MdiMainWindow(QtGui.qApp)
mdi.show()
mw=getMainWindow()
#mw.hide() # hide all
if __name__ == '__main__':
main()
Ab FreeCAD rev2760 (2010, 1) ist es nun möglich, die Coin-Darstellung eines beliebigen FreeCAD-Objekts zu erhalten, ohne das Hauptfenster zu öffnen. Dies macht es extrem einfach, einen eigenen Viewer zu implementieren und ihn transparent von FreeCAD aktualisieren zu lassen. Nach dem Importieren des Moduls FreeCADGui muss man es mit der Methode setupWithoutGUI() starten. Danach können alle View-Provider von FreeCAD verwendet werden, um OpenInventor (Coin)-Knoten zu erhalten.
import os, sys, FreeCAD, FreeCADGui
from PyQt4 import QtCore, QtGui
from PyQt4.QtGui import QMainWindow, QWorkspace, QAction, QFileDialog, QApplication
from pivy.coin import SoInput, SoDB, sogui
class MdiMainWindow(QMainWindow):
def __init__(self, qApp):
QMainWindow.__init__(self)
self._firstwidget = None
self._workspace = QWorkspace()
self.setCentralWidget(self._workspace)
self.setAcceptDrops(True)
self.setWindowTitle("Pivy Quarter MDI example")
self.viewers=[]
filemenu = self.menuBar().addMenu("&File")
windowmenu = self.menuBar().addMenu("&Windows")
fileopenaction = QAction("&Create Box", self)
fileexitaction = QAction("E&xit", self)
tileaction = QAction("Tile", self)
cascadeaction = QAction("Cascade", self)
filemenu.addAction(fileopenaction)
filemenu.addAction(fileexitaction)
windowmenu.addAction(tileaction)
windowmenu.addAction(cascadeaction)
self.connect(fileopenaction, QtCore.SIGNAL("triggered()"), self.createBoxInFreeCAD)
self.connect(fileexitaction, QtCore.SIGNAL("triggered()"), QtGui.qApp.closeAllWindows)
self.connect(tileaction, QtCore.SIGNAL("triggered()"), self._workspace.tile)
self.connect(cascadeaction, QtCore.SIGNAL("triggered()"), self._workspace.cascade)
windowmapper = QtCore.QSignalMapper(self)
self.connect(windowmapper, QtCore.SIGNAL("mapped(QWidget *)"), self._workspace.setActiveWindow)
def closeEvent(self, event):
self._workspace.closeAllWindows()
def createBoxInFreeCAD(self):
widget = QtGui.QWidget(self._firstwidget)
viewer = sogui.SoGuiExaminerViewer(widget)
self._workspace.addWindow(widget)
if not self._firstwidget:
self._firstwidget = widget
widget.show()
self.viewers.append(viewer)
doc = FreeCAD.newDocument()
obj=doc.addObject("Part::Box","myBox")
doc.recompute()
root=FreeCADGui.subgraphFromObject(obj)
viewer.setSceneGraph(root)
def main():
app = QApplication(sys.argv)
mdi = MdiMainWindow(app)
mdi.show()
FreeCADGui.setupWithoutGUI()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Wenn die Verwendung des sogui-Moduls von pivy nicht funktioniert (das sogui-Modul wird zunehmend veraltet sein und die Entwickler von coin bevorzugen nun die neue quarter-Bibliothek, die eine viel bessere Interaktion mit qt bietet), findet man hier dasselbe Skript, jedoch unter Verwendung von quarter:
#!/usr/bin/env python
import os
import sys
from PyQt4 import QtCore, QtGui
from PyQt4.QtGui import QMainWindow, QWorkspace, QAction, QApplication
from pivy.coin import SoInput, SoDB
from pivy.quarter import QuarterWidget
import FreeCADGui
class MdiQuarterWidget(QuarterWidget):
def __init__(self, parent, sharewidget):
QuarterWidget.__init__(self, parent=parent, sharewidget=sharewidget)
def minimumSizeHint(self):
return QtCore.QSize(640, 480)
class MdiMainWindow(QMainWindow):
def __init__(self, qApp):
QMainWindow.__init__(self)
self._firstwidget = None
self._workspace = QWorkspace()
self.setCentralWidget(self._workspace)
self.setAcceptDrops(True)
self.setWindowTitle("Pivy Quarter MDI example")
filemenu = self.menuBar().addMenu("&File")
windowmenu = self.menuBar().addMenu("&Windows")
fileopenaction = QAction("&Create Box", self)
fileexitaction = QAction("E&xit", self)
tileaction = QAction("Tile", self)
cascadeaction = QAction("Cascade", self)
filemenu.addAction(fileopenaction)
filemenu.addAction(fileexitaction)
windowmenu.addAction(tileaction)
windowmenu.addAction(cascadeaction)
self.connect(fileopenaction, QtCore.SIGNAL("triggered()"), self.createBoxInFreeCAD)
self.connect(fileexitaction, QtCore.SIGNAL("triggered()"), QtGui.qApp.closeAllWindows)
self.connect(tileaction, QtCore.SIGNAL("triggered()"), self._workspace.tile)
self.connect(cascadeaction, QtCore.SIGNAL("triggered()"), self._workspace.cascade)
windowmapper = QtCore.QSignalMapper(self)
self.connect(windowmapper, QtCore.SIGNAL("mapped(QWidget *)"), self._workspace.setActiveWindow)
self.dirname = os.curdir
def closeEvent(self, event):
self._workspace.closeAllWindows()
def createBoxInFreeCAD(self):
d=FreeCAD.newDocument()
o=d.addObject("Part::Box")
d.recompute()
s=FreeCADGui.subgraphFromObject(o)
child = self.createMdiChild()
child.show()
child.setSceneGraph(s)
def createMdiChild(self):
widget = MdiQuarterWidget(None, self._firstwidget)
self._workspace.addWindow(widget)
if not self._firstwidget:
self._firstwidget = widget
return widget
def main():
app = QApplication(sys.argv)
FreeCADGui.setupWithoutGUI()
mdi = MdiMainWindow(app)
mdi.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Im Quellcode gibt es Beispiele für die Einbettung von FreeCAD mit verschiedenen Grafik-Toolkits: